package com.wuhao.code.check.model

import com.aegis.mybatis.xmlless.annotations.*
import com.aegis.mybatis.xmlless.constant.ClassNames
import com.aegis.mybatis.xmlless.enums.TestType
import com.aegis.mybatis.xmlless.model.ChooseCriteriaInfo
import com.aegis.mybatis.xmlless.model.JoinObjectInfo
import com.aegis.mybatis.xmlless.model.base.BaseClassWrapper
import com.aegis.mybatis.xmlless.resolver.ParameterResolver
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.wuhao.code.check.*
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClass


/**
 * 是否基本类型
 *
 * @return
 */
fun PsiElement.isPrimitive(): Boolean {
  if (this is KtClass) {
    return this.fqName?.toString() in listOf(
        "kotlin.Boolean", "kotlin.Int",
        "kotlin.Long", "kotlin.Double", "kotlin.Float", "kotlin.Short", "kotlin.Byte", "kotlin.Char"
    )
  }
  return false
}

/**
 * @return
 */
fun PsiElement.isEnum(): Boolean {
  return when (this) {
    is KtClass  -> this.isEnum()
    is PsiClass -> this.isEnum
    else        -> false
  }
}

/**
 * @return
 */
fun PsiElement.getFullName(): String {
  return when (this) {
    is KtClass  -> this.fqName?.toString() ?: ""
    is PsiClass -> this.qualifiedName ?: ""
    else        -> ""
  }
}

/**
 * @return
 */
fun PsiElement.getAllSuperClassNames(): List<String> {
  return this.getAllSuperClasses().mapNotNull {
    when (it) {
      is KtClass  -> it.fqName?.toString()
      is PsiClass -> it.qualifiedName
      else        -> null
    }
  }.distinct()
}

fun PsiElement.getAllSuperClasses(): Set<PsiElement> {
  return when (this) {
    is PsiClass    -> {
      this.getAllSuperClasses()
    }

    is KtClass     -> {
      this.getAllSuperClasses()
    }

    is KtTypeAlias -> {
      this.getTypeReference()?.typeElement?.actualType?.resolveKtClass()?.getAllSuperClasses()?.toSet() ?: setOf()
    }

    else           -> setOf()
  }
}

/**
 * @return
 */
fun KtClass.getAllSuperClasses(): Set<PsiElement> {
  val set = mutableSetOf<PsiElement>()
  set.add(this)
  if (this.name !in listOf("Any", "Object")) {
    this.superTypeListEntries.map {
      val superType = it.typeReference?.typeElement?.actualType?.resolveKtClass()
      if (superType != null) {
        set.addAll(superType.getAllSuperClasses())
      }
    }
  }
  return set
}

fun PsiElement.isComplexType(): Boolean {
  val allInterfaces = this.getAllSuperClassNames()
  val name = this.getFullName()
  if (this.isPrimitive()) {
    return false
  }
  return !this.isPrimitive()
      && !this.hasAnnotation(JsonMappingProperty::class.java)
      && name != "kotlin.Array"
      && !this.isEnum()
      && !ParameterResolver.isNotComplexType(name, allInterfaces)
}

/**
 * @return
 */
private fun PsiClass.getAllSuperClasses(): Set<PsiElement> {
  val set = mutableSetOf<PsiElement>()
  if (this.name == "Object") {
    return set
  }
  set.add(this)
  if (this.name != "Object") {
    this.superTypes.map {
      val target = it.resolve() ?: return set
      set.addAll(target.getAllSuperClasses())
    }
    this.superClass?.let {
      set.addAll(it.getAllSuperClasses())
    }
  }
  return set
}

/**
 *
 * @author 吴昊
 * @date 2024/1/18 10:02
 * @since v0.0.0
 * @version 1.0
 */
abstract class KtAnnotatedWrapper<T : KtAnnotated>(
    element: T
) : BasePsiAnnotatedElementWrapper<T>(element) {

  abstract fun getTypeElement(): KtTypeElement?

  override fun getChooseCriteria(): ChooseCriteriaInfo {
    return createChooseFromWhenList() ?: ChooseCriteriaInfo(
        element.annotationEntries.filter {
          it.shortName?.asString() == TestCriteria::class.java.simpleName
        }.map {
          val test = it.getAttributeValue(TestCriteria::ifExpression.name)!!.toStringValue()!!
          val sql = it.getAttributeValue(TestCriteria::sqlExpression.name)!!.toStringValue()!!
          TestCriteria(test, sql)
        }, ""
    )
  }

  override fun getParameterTestTypeList(): List<TestType> {
    return element.getStringListAnnotationValue(Test::class.java.name, "value").map {
      TestType.valueOf(it)
    }
  }

  override fun getJoinPropertyList(): List<JoinObjectInfo> {
    if (!element.hasAnnotation(FeignProperty::class.java.name)) {
      return listOf()
    }
    return element.annotationEntries.filter { it.shortName?.asString() == FeignProperty::class.java.simpleName }.map {
      resolveJoinObjectFromKtAnnotation(it)
    }
  }

  override fun getType(): BaseClassWrapper {
    when (val el = this.getTypeElement()?.actualType?.resolveKtClass()) {
      is KtClass     -> {
        return KtClassWrapper(el)
      }

      is PsiClass    -> {
        return PsiClassWrapper(el)
      }

      is KtTypeAlias -> {
        val realType = el.getTypeReference()?.typeElement?.actualType?.resolveKtClass()
        if (realType is KtClass) {
          return KtClassWrapper(realType)
        } else if (realType is PsiClass) {
          return PsiClassWrapper(realType)
        }
      }
    }
    error("无法识别${element.containingClass()?.fqName?.toString()}.${element.name}的类型")
  }

  override fun isCollection(): Boolean {
    val typeClass = getElementClass()
    if (typeClass != null) {
      val allSuperClasses = typeClass.getAllSuperClassNames()
      return allSuperClasses.contains(ClassNames.KOTLIN_COLLECTION)
    }
    return false
  }

  override fun isString(): Boolean {
    return getTypeElement()?.actualType?.text == "String"
  }

  override fun getName(): String {
    return element.name ?: "UNKNOWN_PROPERTY"
  }

  override fun getTypeHandlerName(): String? {
    val handler = element.getAnnotationPropertyValue(Handler::class.java.name, "value")
    if (handler is KtClassLiteralExpression) {
      val exp = handler.receiverExpression
      if (exp is KtNameReferenceExpression) {
        val clazz = exp.resolve()
        if (clazz is KtClass) {
          return clazz.fqName?.toString()
        }
      }
    }
    return null
  }

  override fun isPrimitive(): Boolean {
    val typeElement = getTypeElement()
    if (typeElement is KtNullableType) {
      return false
    } else if (typeElement is KtUserType) {
      val ref = typeElement.referenceExpression?.resolve()
      if (ref is KtClass) {
        return ref.fqName?.toString() in listOf(
            "kotlin.Boolean", "kotlin.Int",
            "kotlin.Long", "kotlin.Double", "kotlin.Float", "kotlin.Short", "kotlin.Byte", "kotlin.Char"
        )
      }
    }
    return false
  }

  private fun getElementClass(): PsiElement? {
    val ref = getTypeElement()?.actualType?.resolveKtClass()
    if (ref is KtClass) {
      return ref
    } else if (ref is PsiClass) {
      return ref
    }
    return null
  }

}
